/*
 * DBDumper.java	2008/07/25
 *
 * Copyright 2008 Web & Zen Multimedia, All rights reserved.
 * Use is subject to license terms.
 */
package it.wzm.lightweightlog;

import java.util.Properties;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

/**
 * Used for dumping entries in a given database table (or default one). This dumper can connect to database with two oprions:
 * <ol>
 *    <li>JNDI </li>
 *    <li>with usr, pwd, url, driver</li>
 * </ol>
 *
 * If option 2 is choosen then driver has to be in classpath.<br>
 * In addition of the property needed by Dumper superclass, this is a listing of all the other properties, all of them must be prefixed
 * following the rule of {@link Dumper}:
 * <ul>
 *     <li>usr = the user to connect to db</li>
 *     <li>pwd = the password to conect to db</li>
 *     <li>url = the url of the database</li>
 *     <li>driver = the driver </li>
 *     <li>jndi = es. jdbc/mydb</li>
 *     <li>pstmt = the statement to be used (optional)</li>
 * </ul>
 * @author Leonardo Celati
 */
public class DBDumper extends Dumper {    
    
    /**
     * As required by serialization
     */
    private static final long serialVersionUID = -1653710407552626676L;
    /**
     * Url database
     */
    private String url;
    /**
     * User access
     */
    private String usr;
    /**
     * Password access
     */
    private String pwd;
    /**
     * DB driver
     */
    private String driver;
    /**
     * Prepared statement string
     */
    private String pstmt;
    /**
     * Prepared statement itself
     */
    private PreparedStatement ps;
    /**
     * Connection
     */
    private Connection c;
    /**
     * The configured jndi
     */
    private String jndi;
    /**
     * The datasource
     */
    private DataSource ds;
    /**
     * The context
     */
    private InitialContext ctxt;
    /**
     * Mode for connection
     */
    enum Mode {
        /**
         * JNDI connection mode
         */
        JNDI,
        /**
         * Usr,Pwd connection mode
         */
        APP,
    }
    /**
     * Mode connection
     */
    private Mode mode;
    
    
    /**
     * Getter for url
     * @return the url
     */
    protected final String getUrl() {
        return url;
    }
    
    /**
     * Getter for usr
     * @return the usr
     */
    protected final String getUsr() {
        return usr;
    }
    
    /**
     * Getter for driver
     * @return the driver
     */
    protected final String getDriver() {
        return driver;
    }
    
    /**
     * Getter for pstmt
     * @return the pstmt
     */
    protected final String getPstmt() {
        return pstmt;
    }
    
    /**
     * Getter for ps
     * @return the ps
     */
    protected final PreparedStatement getPs() {
        return ps;
    }
    
    /**
     * Getter for Connection
     * @return the connection
     */
    protected final Connection getC() {
        return c;
    }
    
    /**
     * Getter for JNDI
     * @return the jndi
     */
    protected final String getJndi() {
        return jndi;
    }
    
    /**
     * Getter for DataSource
     * @return the ds
     */
    protected final DataSource getDs() {
        return ds;
    }
    
    /**
     * Getter for Context
     * @return the ctxt
     */
    protected final InitialContext getCtxt() {
        return ctxt;
    }
    
    /**
     * Getter for Mode
     * @return the mode
     */
    public final Mode getMode() {
        return mode;
    }
    
    /**
     * Build a dumper from a given name and properties set
     * @param name The name of the Dumper as declared in dumper.properties
     * @param p Set of properties
     */
    public DBDumper(String name, Properties p) {
        // calling super constructor
        super(name, p);
        
        // look if we defined a jndi context...
        if (this.get("jndi") != null) {
            try {
                ctxt = new InitialContext();
                ds = (DataSource) ctxt.lookup(this.get("jndi"));
                // setting mode...
                this.mode = Mode.JNDI;
            } catch (NamingException e) {
                throw new LightweightlogInitializationException(e);
            }
            
        // otherwise...using normal parameters...
        } else {
            // trying to get db parameter...
            this.driver=String.valueOf(this.get("driver"));
            this.pwd=String.valueOf(this.get("pwd"));
            this.url=String.valueOf(this.get("url"));
            this.usr=String.valueOf(this.get("usr"));
            
            // we try to load the driver directly on the constructor
            // so we let the open and close method only dealing
            // with Connection and PreparedStatement....
            // The class not found exception throws a Runtime exception...
            try {
                Class.forName(driver);
            } catch (ClassNotFoundException e) {
                throw new LightweightlogInitializationException(e);
            }            
            // setting mode...
            this.mode = Mode.APP;
        }        
        // if we don't find a prepared statement
        // we use the default one
        if (this.get("pstmt")!=null) {
            this.pstmt=String.valueOf(this.get("pstmt"));
        } else {
            this.pstmt=DEFAULT_PREPARED_STMT;
        }        
    }
    
    /**
     * Tell wether this Dumper is open or closed, which is checking the Connection to database
     */
    @Override
    public boolean isOpen() {
    	try {
			return (!c.isClosed());
		} catch (SQLException e) {
			return false;
		} catch (NullPointerException e) {
			return false;
		}
    }
    
    /**
     * Open connection to database
     * @throws it.wzm.lightweightlog.DumperException If something went wrong
     */
    @Override
    public void open() throws DumperException {
        try {
            // choose what mode we set...
            switch (this.mode) {
                // we manage connection with parameters...
                case APP:
                    c = DriverManager.getConnection(url, usr, pwd);
                    break;
                    // we previously set up a JNDI...
                case JNDI:
                    c = ds.getConnection();
                    break;
            }
            // prepare statement...
            ps = c.prepareStatement(pstmt);
            
        } catch (SQLException e) {
            throw new DumperException(e.getMessage());
        }
    }
    
    /**
     * Close our db connection
     * @throws it.wzm.lightweightlog.DumperException if something fail
     */
    @Override
    public void close() throws DumperException {
        try {
            // let's try first with sql standard...
            if (ps!=null) {
                ps.clearParameters();
                ps.close();
            }
            if (c!=null) {
                c.close();
            }
            
        } catch (SQLException e) {
            throw new DumperException("cannot close DBDumper SQL " + e.getMessage());
        }
    }
    
    
    /**
     * Write Entry to Database
     * @param e Anything extending Entry
     * @throws it.wzm.lightweightlog.DumperException if something fail
     */
    @Override
	public <E extends Entry> void dump(E e) throws DumperException {

        try {
            // just in case
            ps.clearParameters();
            // following the order we set parameter
            // timestamp, message, severity, classname, name
            ps.setTimestamp(1, new java.sql.Timestamp(e.getTimestamp()));
            ps.setString(2, e.getMessage());
            ps.setString(3, e.getSeverity().toString());
            ps.setString(4, e.getClassname());
            ps.setString(5, e.getName());
            
            // ready to execute
            boolean success = ps.execute();
            if (!success) {
                throw new DumperException("Insert fail");
            }
        } catch (SQLException ex) {
            throw new DumperException(ex);
        }
    }
    
    /**
     * Kill when necessary
     */
    @Override
    public void kill() {
        try {
            // normally closing...
            this.close();
        } catch (Exception e) {
            // we do nothing...
        } finally {
            // finally the context...
            if (ctxt != null) {
                try {
                    ctxt.close();
                } catch (NamingException e) {
                    // doing nothing...
                }
            }
        }
    }
    
}
